package com.zaodei.rxjob.core.config;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

//开启缓存支持
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
	
	@Value("${spring.redis.password}")
	private String password;
	@Value("${spring.redis.timeout}")
	private String timeout;
	@Value("${spring.redis.cluster.maxRedirects}")
	private Integer maxRedirects;
	@Value("${spring.redis.cluster.nodes}")
	private String nodes;
	@Value("${spring.redis.lettuce.pool.maxIdle}")
	private Integer maxIdle;
	@Value("${spring.redis.lettuce.pool.minIdle}")
	private Integer minIdle;
	@Value("${spring.redis.lettuce.pool.maxTotal}")
	private Integer maxTotal;
	@Value("${spring.redis.lettuce.pool.maxWait}")
	private Long maxWait;

	// 缓存管理器
	@Bean
	public CacheManager cacheManager(
			RedisConnectionFactory redisConnectionFactory) {

		RedisCacheConfiguration config = RedisCacheConfiguration
				.defaultCacheConfig()
				.serializeKeysWith(
						RedisSerializationContext.SerializationPair
								.fromSerializer(keySerializer()))
				// key序列化方式
				.serializeValuesWith(
						RedisSerializationContext.SerializationPair
								.fromSerializer(valueSerializer()))
				// value序列化方式
				.disableCachingNullValues()
				.entryTtl(Duration.ofSeconds(30 * 60));// 缓存过期时间

		RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder
				.fromConnectionFactory(lettuceConnectionFactory())
				.cacheDefaults(config)
				.transactionAware()
				.withInitialCacheConfigurations(getRedisCacheConfigurationMap());

		return builder.build();
	}

	private RedisSerializer<String> keySerializer() {
		return new StringRedisSerializer();
	}

	private RedisSerializer<Object> valueSerializer() {
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
				Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

		om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

		jackson2JsonRedisSerializer.setObjectMapper(om);
		return jackson2JsonRedisSerializer;
	}

	private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
		Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
		// SsoCache和BasicDataCache进行过期时间配置
		redisCacheConfigurationMap.put("menuCache",
				this.getRedisCacheConfigurationWithTtl(24 * 60 * 60));
		redisCacheConfigurationMap.put("BasicDataCache",
				this.getRedisCacheConfigurationWithTtl(30 * 60));
		return redisCacheConfigurationMap;
	}

	private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(
			Integer seconds) {

		RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
				.defaultCacheConfig();
		redisCacheConfiguration = redisCacheConfiguration
				.entryTtl(Duration.ofSeconds(seconds))
				.serializeKeysWith(
						RedisSerializationContext.SerializationPair
								.fromSerializer(keySerializer()))// key序列化方式
				.serializeValuesWith(
						RedisSerializationContext.SerializationPair
								.fromSerializer(valueSerializer()))// value序列化方式;
		;
		return redisCacheConfiguration;
	}

	@Bean(name = "cacheKeyGenerator")
	public KeyGenerator cacheKeyGenerator() {
		return new KeyGenerator() {
			@Override
			public Object generate(Object target, Method method,
					Object... params) {

				StringBuffer sb = new StringBuffer();
				sb.append(target.getClass().getName());
				sb.append(method.getName());
				for (Object obj : params) {
					sb.append(obj.toString());
				}
				return sb.toString();
			}
		};
	}

	/**
	 * RedisTemplate配置
	 */
	@Bean
	public RedisTemplate<String, Object> redisTemplate(
			LettuceConnectionFactory lettuceConnectionFactory) {
		// 设置序列化
		//
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setKeySerializer(keySerializer());
		redisTemplate.setHashKeySerializer(keySerializer());
		// redisTemplate.setHashValueSerializer(valueSerializer());
		// redisTemplate.setValueSerializer(valueSerializer());
		redisTemplate.setConnectionFactory(lettuceConnectionFactory);
		return redisTemplate;
	}

	@SuppressWarnings("rawtypes")
	@Bean
	LettuceConnectionFactory lettuceConnectionFactory() {
		// 连接池配置
		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
		poolConfig.setMaxIdle(maxIdle == null ? 8 : maxIdle);
		poolConfig.setMinIdle(minIdle == null ? 1 : minIdle);
		poolConfig.setMaxTotal(maxTotal == null ? 8 : maxTotal);
		poolConfig.setMaxWaitMillis(maxWait == null ? 5000L : maxWait);
		LettucePoolingClientConfiguration lettucePoolingClientConfiguration = LettucePoolingClientConfiguration
				.builder().poolConfig(poolConfig).build();
		// 集群redis
		RedisClusterConfiguration redisConfig = new RedisClusterConfiguration();
		Set<RedisNode> nodeses = new HashSet<>();
		String[] hostses = nodes.split(",");
		for (String h : hostses) {
			h = h.replaceAll("\\s", "").replaceAll("\n", "");
			if (!"".equals(h)) {
				String host = h.split(":")[0];
				int port = Integer.valueOf(h.split(":")[1]);
				nodeses.add(new RedisNode(host, port));
			}
		}
		redisConfig.setClusterNodes(nodeses);
		// 跨集群执行命令时要遵循的最大重定向数量
		redisConfig.setMaxRedirects(maxRedirects);
		redisConfig.setPassword(password);
		return new LettuceConnectionFactory(redisConfig,
				lettucePoolingClientConfiguration);
	}
}